package spring.security.core.lock;

import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.SerializationUtils;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import spring.security.core.cache.redis.RedisCacheProvider;
import spring.security.core.cache.redis.client.RedisClient;
import spring.security.core.lock.annotation.LockKey;
import spring.security.core.lock.annotation.SyncLock;
import spring.security.core.util.StringUtils;

@SuppressWarnings({"unchecked", "rawtypes"})
public class RedisLockHandler {
	private static final Logger logger = LoggerFactory.getLogger(RedisLockHandler.class);
	private final ThreadLocal<SyncLock> threadLocal = new ThreadLocal();
	private final Map<String, Set<String>> subLockKey = Maps.newHashMap();
	private RedisCacheProvider redisCacheProvider;
	private int maxLockTime = 7200;
	private String namespace = "sys:lock:pms";

	public RedisCacheProvider getRedisCacheProvider() {
		return this.redisCacheProvider;
	}

	public void setRedisCacheProvider(RedisCacheProvider redisCacheProvider) {
		this.redisCacheProvider = redisCacheProvider;
	}

	public String getNamespace() {
		return this.namespace;
	}

	public void setNamespace(String namespace) {
		this.namespace = namespace;
	}

	public int getMaxLockTime() {
		return this.maxLockTime;
	}

	public void setMaxLockTime(int maxLockTime) {
		this.maxLockTime = maxLockTime;
	}

	public ThreadLocal<SyncLock> getThreadLocal() {
		return this.threadLocal;
	}

	public Object lock(ProceedingJoinPoint pjp) throws Throwable {
		SyncLock syncLock = (SyncLock) this.threadLocal.get();
		if (syncLock != null && System.currentTimeMillis() > this.getLock(syncLock.key())) {
			logger.info("忽略嵌套锁,key:{}", syncLock.key());
			return pjp.proceed();
		} else {
			syncLock = this.getLockInfo(pjp);
			if (syncLock == null) {
				throw new IllegalArgumentException("业务级同步锁参数错误");
			} else {
				this.threadLocal.set(syncLock);
				String lockKey = this.getLockKey(pjp, syncLock.key());
				boolean arg13 = false;

				Object conf1;
				label215: {
					Object subKey;
					try {
						arg13 = true;
						if (lockKey == null) {
							conf1 = pjp.proceed();
							arg13 = false;
							break label215;
						}

						SyncLockConf conf = new SyncLockConf(syncLock);
						subKey = this.process(conf, lockKey, pjp);
						arg13 = false;
					} finally {
						if (arg13) {
							this.threadLocal.remove();
							if (!this.subLockKey.isEmpty()) {
								Set subKey2 = (Set) this.subLockKey.get(syncLock.key());
								if (subKey2 != null && !subKey2.isEmpty()) {
									Iterator arg10 = subKey2.iterator();

									while (arg10.hasNext()) {
										String sk2 = (String) arg10.next();
										this.releaseLock(sk2);
									}
								}

								this.subLockKey.remove(syncLock.key());
							}

							if (lockKey != null) {
								this.releaseLock(lockKey);
							}

						}
					}

					this.threadLocal.remove();
					if (!this.subLockKey.isEmpty()) {
						Set subKey1 = (Set) this.subLockKey.get(syncLock.key());
						if (subKey1 != null && !subKey1.isEmpty()) {
							Iterator sk = subKey1.iterator();

							while (sk.hasNext()) {
								String sk1 = (String) sk.next();
								this.releaseLock(sk1);
							}
						}

						this.subLockKey.remove(syncLock.key());
					}

					if (lockKey != null) {
						this.releaseLock(lockKey);
					}

					return subKey;
				}

				this.threadLocal.remove();
				if (!this.subLockKey.isEmpty()) {
					Set subKey3 = (Set) this.subLockKey.get(syncLock.key());
					if (subKey3 != null && !subKey3.isEmpty()) {
						Iterator subKey4 = subKey3.iterator();

						while (subKey4.hasNext()) {
							String sk3 = (String) subKey4.next();
							this.releaseLock(sk3);
						}
					}

					this.subLockKey.remove(syncLock.key());
				}

				if (lockKey != null) {
					this.releaseLock(lockKey);
				}

				return conf1;
			}
		}
	}

	public void lock(SyncLockConf syncLockConf, String lockKey) throws RuntimeException {
		if (syncLockConf != null && !StringUtils.isBlank(lockKey)) {
			if (logger.isDebugEnabled()) {
				logger.debug("手动对子资源加锁:{} - {}", syncLockConf.getKey(), lockKey);
			}

			try {
				this.process(syncLockConf, lockKey, (ProceedingJoinPoint) null);
				if (this.subLockKey.containsKey(syncLockConf.getKey())) {
					((Set) this.subLockKey.get(syncLockConf.getKey())).add(lockKey);
				} else {
					HashSet e = Sets.newHashSet();
					e.add(lockKey);
					this.subLockKey.put(syncLockConf.getKey(), e);
				}

			} catch (Throwable arg3) {
				throw new RuntimeException(arg3);
			}
		} else {
			throw new IllegalArgumentException("方法级同步锁参数错误");
		}
	}

	private Object process(SyncLockConf syncLockConf, String lockKey, ProceedingJoinPoint pjp) throws Throwable {
		boolean lock = false;
		Object obj = null;
		long obtainTimeout = System.currentTimeMillis() + syncLockConf.getObtainTimeout();

		while (!lock) {
			long timeout = System.currentTimeMillis() + syncLockConf.getTimeout();
			lock = this.setIfAbsent(lockKey, Long.valueOf(timeout));
			if (lock) {
				if (pjp != null) {
					obj = pjp.proceed();
				}

				return obj;
			}

			if (syncLockConf.isFailfast()) {
				logger.info("请求的资源已被锁定{}.", lockKey);
				throw new LockedResourceException("请求的资源已被锁定.", "7000");
			}

			if (syncLockConf.getTimeout() <= 0L) {
				if (syncLockConf.getObtainTimeout() > 0L && System.currentTimeMillis() > obtainTimeout) {
					logger.info("获取锁资源{}等待超时.", lockKey);
					throw new LockedResourceException("获取锁资源等待超时", "7001");
				}

				TimeUnit.MILLISECONDS.sleep(100L);
			} else if (System.currentTimeMillis() > this.getLock(lockKey)
					&& System.currentTimeMillis() > this.getSet(lockKey, Long.valueOf(timeout))) {
				lock = true;
				if (pjp != null) {
					obj = pjp.proceed();
				}
			} else {
				if (syncLockConf.getObtainTimeout() > 0L && System.currentTimeMillis() > obtainTimeout) {
					logger.info("获取锁资源{}等待超时.", lockKey);
					throw new LockedResourceException("获取锁资源等待超时", "7002");
				}

				TimeUnit.MILLISECONDS.sleep(100L);
			}
		}

		return obj;
	}

	private String getLockKey(ProceedingJoinPoint pjp, String key) {
		String lockKeyStr = this.namespace + ":" + key + ":";
		Object[] args = pjp.getArgs();
		boolean doLock = false;
		if (args != null && args.length > 0) {
			MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
			Annotation[][] paramAnnotations = methodSignature.getMethod().getParameterAnnotations();
			ArrayList keyList = Lists.newArrayList();

			for (int i = 0; i < paramAnnotations.length; ++i) {
				LockKey lockKey = (LockKey) getAnnotation(LockKey.class, paramAnnotations[i]);
				if (lockKey != null) {
					Object arg = args[i];
					if (arg != null) {
						keyList.add(arg.toString());
					}

					if (!doLock) {
						doLock = true;
					}
				}
			}

			if (!keyList.isEmpty()) {
				Collections.sort(keyList);
				lockKeyStr = lockKeyStr + StringUtils.join(keyList, "_");
			}
		}

		return doLock ? lockKeyStr : null;
	}

	private static <T extends Annotation> T getAnnotation(Class<T> annotationClass, Annotation[] annotations) {
		if (annotations != null && annotations.length > 0) {
			Annotation[] arg1 = annotations;
			int arg2 = annotations.length;

			for (int arg3 = 0; arg3 < arg2; ++arg3) {
				Annotation annotation = arg1[arg3];
				if (annotationClass.equals(annotation.annotationType())) {
					return (T) annotation;
				}
			}
		}

		return null;
	}

	private SyncLock getLockInfo(ProceedingJoinPoint pjp) {
		MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
		Method method = methodSignature.getMethod();
		SyncLock lockInfo = (SyncLock) method.getAnnotation(SyncLock.class);
		return lockInfo;
	}

	public boolean setIfAbsent(String key, Long value) {
		byte[] keys = getKey(key);
		byte[] vals = SerializationUtils.serialize(Long.valueOf(value.longValue()));
		if (this.redisCacheProvider.getResource().setnx(keys, vals).longValue() == 1L) {
			if (this.maxLockTime > 0) {
				this.redisCacheProvider.getResource().expire(keys, this.maxLockTime);
			}

			return true;
		} else {
			return false;
		}
	}

	public long getLock(String key) {
		byte[] bytes = this.redisCacheProvider.getResource().get(getKey(key));
		return bytes != null ? ((Long) SerializationUtils.deserialize(bytes)).longValue() : -1L;
	}

	public long getSet(String key, Long value) {
		byte[] keys = getKey(key);
		byte[] vals = SerializationUtils.serialize(Long.valueOf(value.longValue()));
		byte[] results = this.redisCacheProvider.getResource().getSet(keys, vals);
		return results == null ? 0L : ((Long) SerializationUtils.deserialize(results)).longValue();
	}

	public void releaseLock(String key) {
		if (logger.isDebugEnabled()) {
			logger.debug("释放资源锁:{}", key);
		}

		RedisClient redisClient = this.redisCacheProvider.getResource();
		redisClient.del(getKey(key));
	}

	public void releaseSubLock(String key, String lockKey) {
		if (logger.isDebugEnabled()) {
			logger.debug("手动释放子资源锁:{} - {}", key, lockKey);
		}

		RedisClient redisClient = this.redisCacheProvider.getResource();
		if (redisClient.del(getKey(lockKey)).longValue() > 0L) {
			Set sets = (Set) this.subLockKey.get(key);
			if (sets != null) {
				sets.remove(lockKey);
			}
		}

	}

	private static byte[] getKey(String key) {
		try {
			return key.getBytes("UTF-8");
		} catch (UnsupportedEncodingException arg1) {
			logger.warn("Key转byte[]失败{}", key);
			return null;
		}
	}
}
